var Prototype =
{
    Version: '1.7',
    ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
    JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
    emptyFunction: function () { },
    K: function (x) { return x; }
};

var Abstract = {};

var Try =
{
    these: function ()
    {
        var returnValue;
        for (var i = 0, length = arguments.length; i < length; i++)
        {
            var lambda = arguments[i];
            try
            {
                returnValue = lambda();
                break;
            } catch (e) { }
        }
        return returnValue;
    }
};

var Class = (function ()
{
    var IS_DONTENUM_BUGGY = (function ()
    {
        for (var p in { toString: 1 })
        {
            if (p === 'toString') return false;
        }
        return true;
    })();

    function subclass() { };
    function create()
    {
        var parent = null, properties = $A(arguments);
        if (Object.isFunction(properties[0]))
            parent = properties.shift();

        function klass()
        {
            this.initialize.apply(this, arguments);
        }

        Object.extend(klass, Class.Methods);
        klass.superclass = parent;
        klass.subclasses = [];

        if (parent)
        {
            subclass.prototype = parent.prototype;
            klass.prototype = new subclass;
            parent.subclasses.push(klass);
        }

        for (var i = 0, length = properties.length; i < length; i++)
            klass.addMethods(properties[i]);

        if (!klass.prototype.initialize)
            klass.prototype.initialize = Prototype.emptyFunction;

        klass.prototype.constructor = klass;
        return klass;
    }

    function addMethods(source)
    {
        var ancestor = this.superclass && this.superclass.prototype,
        properties = Object.keys(source);

        if (IS_DONTENUM_BUGGY)
        {
            if (source.toString != Object.prototype.toString)
                properties.push("toString");
            if (source.valueOf != Object.prototype.valueOf)
                properties.push("valueOf");
        }

        for (var i = 0, length = properties.length; i < length; i++)
        {
            var property = properties[i], value = source[property];
            if (ancestor && Object.isFunction(value) &&
          value.argumentNames()[0] == "$super")
            {
                var method = value;
                value = (function (m)
                {
                    return function () { return ancestor[m].apply(this, arguments); };
                })(property).wrap(method);

                value.valueOf = method.valueOf.bind(method);
                value.toString = method.toString.bind(method);
            }
            this.prototype[property] = value;
        }

        return this;
    }

    return {
        create: create,
        Methods: {
            addMethods: addMethods
        }
    };
})();

(function ()
{
    var _toString = Object.prototype.toString,
      NULL_TYPE = 'Null',
      UNDEFINED_TYPE = 'Undefined',
      BOOLEAN_TYPE = 'Boolean',
      NUMBER_TYPE = 'Number',
      STRING_TYPE = 'String',
      OBJECT_TYPE = 'Object',
      FUNCTION_CLASS = '[object Function]',
      BOOLEAN_CLASS = '[object Boolean]',
      NUMBER_CLASS = '[object Number]',
      STRING_CLASS = '[object String]',
      ARRAY_CLASS = '[object Array]',
      DATE_CLASS = '[object Date]',
      NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
        typeof JSON.stringify === 'function' &&
        JSON.stringify(0) === '0' &&
        typeof JSON.stringify(Prototype.K) === 'undefined';

    function Type(o)
    {
        switch (o)
        {
            case null: return NULL_TYPE;
            case (void 0): return UNDEFINED_TYPE;
        }
        var type = typeof o;
        switch (type)
        {
            case 'boolean': return BOOLEAN_TYPE;
            case 'number': return NUMBER_TYPE;
            case 'string': return STRING_TYPE;
        }
        return OBJECT_TYPE;
    }

    function extend(destination, source)
    {
        for (var property in source)
            destination[property] = source[property];
        return destination;
    }

    function inspect(object)
    {
        try
        {
            if (isUndefined(object)) return 'undefined';
            if (object === null) return 'null';
            return object.inspect ? object.inspect() : String(object);
        } 
        catch (e)
        {
            if (e instanceof RangeError) return '...';
            throw e;
        }
    }

    function toJSON(value)
    {
        return Str('', { '': value }, []);
    }

    function Str(key, holder, stack)
    {
        var value = holder[key],
        type = typeof value;

        if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function')
        {
            value = value.toJSON(key);
        }

        var _class = _toString.call(value);

        switch (_class)
        {
            case NUMBER_CLASS:
            case BOOLEAN_CLASS:
            case STRING_CLASS:
                value = value.valueOf();
        }

        switch (value)
        {
            case null: return 'null';
            case true: return 'true';
            case false: return 'false';
        }

        type = typeof value;
        switch (type)
        {
            case 'string':
                return value.inspect(true);
            case 'number':
                return isFinite(value) ? String(value) : 'null';
            case 'object':

                for (var i = 0, length = stack.length; i < length; i++)
                {
                    if (stack[i] === value) { throw new TypeError(); }
                }
                stack.push(value);

                var partial = [];
                if (_class === ARRAY_CLASS)
                {
                    for (var i = 0, length = value.length; i < length; i++)
                    {
                        var str = Str(i, value, stack);
                        partial.push(typeof str === 'undefined' ? 'null' : str);
                    }
                    partial = '[' + partial.join(',') + ']';
                } else
                {
                    var keys = Object.keys(value);
                    for (var i = 0, length = keys.length; i < length; i++)
                    {
                        var key = keys[i], str = Str(key, value, stack);
                        if (typeof str !== "undefined")
                        {
                            partial.push(key.inspect(true) + ':' + str);
                        }
                    }
                    partial = '{' + partial.join(',') + '}';
                }
                stack.pop();
                return partial;
        }
    }

    function stringify(object)
    {
        return JSON.stringify(object);
    }

    function toQueryString(object)
    {
        return $H(object).toQueryString();
    }

    function toHTML(object)
    {
        return object && object.toHTML ? object.toHTML() : String.interpret(object);
    }

    function keys(object)
    {
        if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
        var results = [];
        for (var property in object)
        {
            if (object.hasOwnProperty(property))
            {
                results.push(property);
            }
        }
        return results;
    }

    function values(object)
    {
        var results = [];
        for (var property in object)
            results.push(object[property]);
        return results;
    }

    function clone(object)
    {
        return extend({}, object);
    }

    function isElement(object)
    {
        return !!(object && object.nodeType == 1);
    }

    function isArray(object)
    {
        return _toString.call(object) === ARRAY_CLASS;
    }

    var hasNativeIsArray = (typeof Array.isArray == 'function')
    && Array.isArray([]) && !Array.isArray({});

    if (hasNativeIsArray)
    {
        isArray = Array.isArray;
    }

    function isHash(object)
    {
        return object instanceof Hash;
    }

    function isFunction(object)
    {
        return _toString.call(object) === FUNCTION_CLASS;
    }

    function isString(object)
    {
        return _toString.call(object) === STRING_CLASS;
    }

    function isNumber(object)
    {
        return _toString.call(object) === NUMBER_CLASS;
    }

    function isDate(object)
    {
        return _toString.call(object) === DATE_CLASS;
    }

    function isUndefined(object)
    {
        return typeof object === "undefined";
    }

    extend(Object, {
        extend: extend,
        inspect: inspect,
        toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
        toQueryString: toQueryString,
        toHTML: toHTML,
        keys: Object.keys || keys,
        values: values,
        clone: clone,
        isElement: isElement,
        isArray: isArray,
        isHash: isHash,
        isFunction: isFunction,
        isString: isString,
        isNumber: isNumber,
        isDate: isDate,
        isUndefined: isUndefined
    });
})();

Object.extend(Function.prototype, (function ()
{
    var slice = Array.prototype.slice;

    function update(array, args)
    {
        var arrayLength = array.length, length = args.length;
        while (length--) array[arrayLength + length] = args[length];
        return array;
    }

    function merge(array, args)
    {
        array = slice.call(array, 0);
        return update(array, args);
    }

    function argumentNames()
    {
        var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
      .replace(/\s+/g, '').split(',');
        return names.length == 1 && !names[0] ? [] : names;
    }

    function bind(context)
    {
        if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
        var __method = this, args = slice.call(arguments, 1);
        return function ()
        {
            var a = merge(args, arguments);
            return __method.apply(context, a);
        }
    }

    function bindAsEventListener(context)
    {
        var __method = this, args = slice.call(arguments, 1);
        return function (event)
        {
            var a = update([event || window.event], args);
            return __method.apply(context, a);
        }
    }

    function curry()
    {
        if (!arguments.length) return this;
        var __method = this, args = slice.call(arguments, 0);
        return function ()
        {
            var a = merge(args, arguments);
            return __method.apply(this, a);
        }
    }

    function delay(timeout)
    {
        var __method = this, args = slice.call(arguments, 1);
        timeout = timeout * 1000;
        return window.setTimeout(function ()
        {
            return __method.apply(__method, args);
        }, timeout);
    }

    function defer()
    {
        var args = update([0.01], arguments);
        return this.delay.apply(this, args);
    }

    function wrap(wrapper)
    {
        var __method = this;
        return function ()
        {
            var a = update([__method.bind(this)], arguments);
            return wrapper.apply(this, a);
        }
    }

    function methodize()
    {
        if (this._methodized) return this._methodized;
        var __method = this;
        return this._methodized = function ()
        {
            var a = update([this], arguments);
            return __method.apply(null, a);
        };
    }

    return {
        argumentNames: argumentNames,
        bind: bind,
        bindAsEventListener: bindAsEventListener,
        curry: curry,
        delay: delay,
        defer: defer,
        wrap: wrap,
        methodize: methodize
    }
})());

(function (proto)
{
    function toISOString()
    {
        return this.getUTCFullYear() + '-' +
      (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
      this.getUTCDate().toPaddedString(2) + 'T' +
      this.getUTCHours().toPaddedString(2) + ':' +
      this.getUTCMinutes().toPaddedString(2) + ':' +
      this.getUTCSeconds().toPaddedString(2) + 'Z';
    }

    function toJSON()
    {
        return this.toISOString();
    }

    if (!proto.toISOString) proto.toISOString = toISOString;
    if (!proto.toJSON) proto.toJSON = toJSON;

})(Date.prototype);

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function (str)
{
    return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

var PeriodicalExecuter = Class.create({
    initialize: function (callback, frequency)
    {
        this.callback = callback;
        this.frequency = frequency;
        this.currentlyExecuting = false;

        this.registerCallback();
    },

    registerCallback: function ()
    {
        this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
    },

    execute: function ()
    {
        this.callback(this);
    },

    stop: function ()
    {
        if (!this.timer) return;
        clearInterval(this.timer);
        this.timer = null;
    },

    onTimerEvent: function ()
    {
        if (!this.currentlyExecuting)
        {
            try
            {
                this.currentlyExecuting = true;
                this.execute();
                this.currentlyExecuting = false;
            } catch (e)
            {
                this.currentlyExecuting = false;
                throw e;
            }
        }
    }
});
Object.extend(String, {
    interpret: function (value)
    {
        return value == null ? '' : String(value);
    },
    specialChar: {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '\\': '\\\\'
    }
});

Object.extend(String.prototype, (function ()
{
    var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
    typeof JSON.parse === 'function' &&
    JSON.parse('{"test": true}').test;

    function prepareReplacement(replacement)
    {
        if (Object.isFunction(replacement)) return replacement;
        var template = new Template(replacement);
        return function (match) { return template.evaluate(match) };
    }

    function gsub(pattern, replacement)
    {
        var result = '', source = this, match;
        replacement = prepareReplacement(replacement);

        if (Object.isString(pattern))
            pattern = RegExp.escape(pattern);

        if (!(pattern.length || pattern.source))
        {
            replacement = replacement('');
            return replacement + source.split('').join(replacement) + replacement;
        }

        while (source.length > 0)
        {
            if (match = source.match(pattern))
            {
                result += source.slice(0, match.index);
                result += String.interpret(replacement(match));
                source = source.slice(match.index + match[0].length);
            } else
            {
                result += source, source = '';
            }
        }
        return result;
    }

    function sub(pattern, replacement, count)
    {
        replacement = prepareReplacement(replacement);
        count = Object.isUndefined(count) ? 1 : count;

        return this.gsub(pattern, function (match)
        {
            if (--count < 0) return match[0];
            return replacement(match);
        });
    }

    function scan(pattern, iterator)
    {
        this.gsub(pattern, iterator);
        return String(this);
    }

    function truncate(length, truncation)
    {
        length = length || 30;
        truncation = Object.isUndefined(truncation) ? '...' : truncation;
        return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
    }

    function strip()
    {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
    }

    function stripTags()
    {
        return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
    }

    function stripScripts()
    {
        return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
    }

    function extractScripts()
    {
        var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
        matchOne = new RegExp(Prototype.ScriptFragment, 'im');
        return (this.match(matchAll) || []).map(function (scriptTag)
        {
            return (scriptTag.match(matchOne) || ['', ''])[1];
        });
    }

    function evalScripts()
    {
        return this.extractScripts().map(function (script) { return eval(script) });
    }

    function escapeHTML()
    {
        return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    function unescapeHTML()
    {
        return this.stripTags().replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
    }

    function toQueryParams(separator)
    {
        var match = this.strip().match(/([^?#]*)(#.*)?$/);
        if (!match) return {};

        return match[1].split(separator || '&').inject({}, function (hash, pair)
        {
            if ((pair = pair.split('='))[0])
            {
                var key = decodeURIComponent(pair.shift()),
            value = pair.length > 1 ? pair.join('=') : pair[0];

                if (value != undefined) value = decodeURIComponent(value);

                if (key in hash)
                {
                    if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
                    hash[key].push(value);
                }
                else hash[key] = value;
            }
            return hash;
        });
    }

    function toArray()
    {
        return this.split('');
    }

    function succ()
    {
        return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
    }

    function times(count)
    {
        return count < 1 ? '' : new Array(count + 1).join(this);
    }

    function camelize()
    {
        return this.replace(/-+(.)?/g, function (match, chr)
        {
            return chr ? chr.toUpperCase() : '';
        });
    }

    function capitalize()
    {
        return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
    }

    function underscore()
    {
        return this.replace(/::/g, '/')
               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
               .replace(/([a-z\d])([A-Z])/g, '$1_$2')
               .replace(/-/g, '_')
               .toLowerCase();
    }

    function dasherize()
    {
        return this.replace(/_/g, '-');
    }

    function inspect(useDoubleQuotes)
    {
        var escapedString = this.replace(/[\x00-\x1f\\]/g, function (character)
        {
            if (character in String.specialChar)
            {
                return String.specialChar[character];
            }
            return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
        });
        if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
        return "'" + escapedString.replace(/'/g, '\\\'') + "'";
    }

    function unfilterJSON(filter)
    {
        return this.replace(filter || Prototype.JSONFilter, '$1');
    }

    function isJSON()
    {
        var str = this;
        if (str.blank()) return false;
        str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
        str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
        str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
        return (/^[\],:{}\s]*$/).test(str);
    }

    function evalJSON(sanitize)
    {
        var json = this.unfilterJSON(),
        cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
        if (cx.test(json))
        {
            json = json.replace(cx, function (a)
            {
                return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            });
        }
        try
        {
            if (!sanitize || json.isJSON()) return eval('(' + json + ')');
        } catch (e) { }
        throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
    }

    function parseJSON()
    {
        var json = this.unfilterJSON();
        return JSON.parse(json);
    }

    function include(pattern)
    {
        return this.indexOf(pattern) > -1;
    }

    function startsWith(pattern)
    {
        return this.lastIndexOf(pattern, 0) === 0;
    }

    function endsWith(pattern)
    {
        var d = this.length - pattern.length;
        return d >= 0 && this.indexOf(pattern, d) === d;
    }

    function empty()
    {
        return this == '';
    }

    function blank()
    {
        return /^\s*$/.test(this);
    }

    function interpolate(object, pattern)
    {
        return new Template(this, pattern).evaluate(object);
    }

    return {
        gsub: gsub,
        sub: sub,
        scan: scan,
        truncate: truncate,
        strip: String.prototype.trim || strip,
        stripTags: stripTags,
        stripScripts: stripScripts,
        extractScripts: extractScripts,
        evalScripts: evalScripts,
        escapeHTML: escapeHTML,
        unescapeHTML: unescapeHTML,
        toQueryParams: toQueryParams,
        parseQuery: toQueryParams,
        toArray: toArray,
        succ: succ,
        times: times,
        camelize: camelize,
        capitalize: capitalize,
        underscore: underscore,
        dasherize: dasherize,
        inspect: inspect,
        unfilterJSON: unfilterJSON,
        isJSON: isJSON,
        evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
        include: include,
        startsWith: startsWith,
        endsWith: endsWith,
        empty: empty,
        blank: blank,
        interpolate: interpolate
    };
})());

var Template = Class.create({
    initialize: function (template, pattern)
    {
        this.template = template.toString();
        this.pattern = pattern || Template.Pattern;
    },

    evaluate: function (object)
    {
        if (object && Object.isFunction(object.toTemplateReplacements))
            object = object.toTemplateReplacements();

        return this.template.gsub(this.pattern, function (match)
        {
            if (object == null) return (match[1] + '');

            var before = match[1] || '';
            if (before == '\\') return match[2];

            var ctx = object, expr = match[3],
          pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;

            match = pattern.exec(expr);
            if (match == null) return before;

            while (match != null)
            {
                var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
                ctx = ctx[comp];
                if (null == ctx || '' == match[3]) break;
                expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
                match = pattern.exec(expr);
            }

            return before + String.interpret(ctx);
        });
    }
});

Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = {};

var Enumerable = (function ()
{
    function each(iterator, context)
    {
        var index = 0;
        try
        {
            this._each(function (value)
            {
                iterator.call(context, value, index++);
            });
        } catch (e)
        {
            if (e != $break) throw e;
        }
        return this;
    }

    function eachSlice(number, iterator, context)
    {
        var index = -number, slices = [], array = this.toArray();
        if (number < 1) return array;
        while ((index += number) < array.length)
            slices.push(array.slice(index, index + number));
        return slices.collect(iterator, context);
    }

    function all(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var result = true;
        this.each(function (value, index)
        {
            result = result && !!iterator.call(context, value, index);
            if (!result) throw $break;
        });
        return result;
    }

    function any(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var result = false;
        this.each(function (value, index)
        {
            if (result = !!iterator.call(context, value, index))
                throw $break;
        });
        return result;
    }

    function collect(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var results = [];
        this.each(function (value, index)
        {
            results.push(iterator.call(context, value, index));
        });
        return results;
    }

    function detect(iterator, context)
    {
        var result;
        this.each(function (value, index)
        {
            if (iterator.call(context, value, index))
            {
                result = value;
                throw $break;
            }
        });
        return result;
    }

    function findAll(iterator, context)
    {
        var results = [];
        this.each(function (value, index)
        {
            if (iterator.call(context, value, index))
                results.push(value);
        });
        return results;
    }

    function grep(filter, iterator, context)
    {
        iterator = iterator || Prototype.K;
        var results = [];

        if (Object.isString(filter))
            filter = new RegExp(RegExp.escape(filter));

        this.each(function (value, index)
        {
            if (filter.match(value))
                results.push(iterator.call(context, value, index));
        });
        return results;
    }

    function include(object)
    {
        if (Object.isFunction(this.indexOf))
            if (this.indexOf(object) != -1) return true;

        var found = false;
        this.each(function (value)
        {
            if (value == object)
            {
                found = true;
                throw $break;
            }
        });
        return found;
    }

    function inGroupsOf(number, fillWith)
    {
        fillWith = Object.isUndefined(fillWith) ? null : fillWith;
        return this.eachSlice(number, function (slice)
        {
            while (slice.length < number) slice.push(fillWith);
            return slice;
        });
    }

    function inject(memo, iterator, context)
    {
        this.each(function (value, index)
        {
            memo = iterator.call(context, memo, value, index);
        });
        return memo;
    }

    function invoke(method)
    {
        var args = $A(arguments).slice(1);
        return this.map(function (value)
        {
            return value[method].apply(value, args);
        });
    }

    function max(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var result;
        this.each(function (value, index)
        {
            value = iterator.call(context, value, index);
            if (result == null || value >= result)
                result = value;
        });
        return result;
    }

    function min(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var result;
        this.each(function (value, index)
        {
            value = iterator.call(context, value, index);
            if (result == null || value < result)
                result = value;
        });
        return result;
    }

    function partition(iterator, context)
    {
        iterator = iterator || Prototype.K;
        var trues = [], falses = [];
        this.each(function (value, index)
        {
            (iterator.call(context, value, index) ?
        trues : falses).push(value);
        });
        return [trues, falses];
    }

    function pluck(property)
    {
        var results = [];
        this.each(function (value)
        {
            results.push(value[property]);
        });
        return results;
    }

    function reject(iterator, context)
    {
        var results = [];
        this.each(function (value, index)
        {
            if (!iterator.call(context, value, index))
                results.push(value);
        });
        return results;
    }

    function sortBy(iterator, context)
    {
        return this.map(function (value, index)
        {
            return {
                value: value,
                criteria: iterator.call(context, value, index)
            };
        }).sort(function (left, right)
        {
            var a = left.criteria, b = right.criteria;
            return a < b ? -1 : a > b ? 1 : 0;
        }).pluck('value');
    }

    function toArray()
    {
        return this.map();
    }

    function zip()
    {
        var iterator = Prototype.K, args = $A(arguments);
        if (Object.isFunction(args.last()))
            iterator = args.pop();

        var collections = [this].concat(args).map($A);
        return this.map(function (value, index)
        {
            return iterator(collections.pluck(index));
        });
    }

    function size()
    {
        return this.toArray().length;
    }

    function inspect()
    {
        return '#<Enumerable:' + this.toArray().inspect() + '>';
    }

    return {
        each: each,
        eachSlice: eachSlice,
        all: all,
        every: all,
        any: any,
        some: any,
        collect: collect,
        map: collect,
        detect: detect,
        findAll: findAll,
        select: findAll,
        filter: findAll,
        grep: grep,
        include: include,
        member: include,
        inGroupsOf: inGroupsOf,
        inject: inject,
        invoke: invoke,
        max: max,
        min: min,
        partition: partition,
        pluck: pluck,
        reject: reject,
        sortBy: sortBy,
        toArray: toArray,
        entries: toArray,
        zip: zip,
        size: size,
        inspect: inspect,
        find: detect
    };
})();

function $A(iterable)
{
    if (!iterable) return [];
    if ('toArray' in Object(iterable)) return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
}

function $w(string)
{
    if (!Object.isString(string)) return [];
    string = string.strip();
    return string ? string.split(/\s+/) : [];
}

Array.from = $A;

(function ()
{
    var arrayProto = Array.prototype,
      slice = arrayProto.slice,
      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available

    function each(iterator, context)
    {
        for (var i = 0, length = this.length >>> 0; i < length; i++)
        {
            if (i in this) iterator.call(context, this[i], i, this);
        }
    }
    if (!_each) _each = each;

    function clear()
    {
        this.length = 0;
        return this;
    }

    function first()
    {
        return this[0];
    }

    function last()
    {
        return this[this.length - 1];
    }

    function compact()
    {
        return this.select(function (value)
        {
            return value != null;
        });
    }

    function flatten()
    {
        return this.inject([], function (array, value)
        {
            if (Object.isArray(value))
                return array.concat(value.flatten());
            array.push(value);
            return array;
        });
    }

    function without()
    {
        var values = slice.call(arguments, 0);
        return this.select(function (value)
        {
            return !values.include(value);
        });
    }

    function reverse(inline)
    {
        return (inline === false ? this.toArray() : this)._reverse();
    }

    function uniq(sorted)
    {
        return this.inject([], function (array, value, index)
        {
            if (0 == index || (sorted ? array.last() != value : !array.include(value)))
                array.push(value);
            return array;
        });
    }

    function intersect(array)
    {
        return this.uniq().findAll(function (item)
        {
            return array.detect(function (value) { return item === value });
        });
    }

    function clone()
    {
        return slice.call(this, 0);
    }

    function size()
    {
        return this.length;
    }

    function inspect()
    {
        return '[' + this.map(Object.inspect).join(', ') + ']';
    }

    function indexOf(item, i)
    {
        i || (i = 0);
        var length = this.length;
        if (i < 0) i = length + i;
        for (; i < length; i++)
            if (this[i] === item) return i;
        return -1;
    }

    function lastIndexOf(item, i)
    {
        i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
        var n = this.slice(0, i).reverse().indexOf(item);
        return (n < 0) ? n : i - n - 1;
    }

    function concat()
    {
        var array = slice.call(this, 0), item;
        for (var i = 0, length = arguments.length; i < length; i++)
        {
            item = arguments[i];
            if (Object.isArray(item) && !('callee' in item))
            {
                for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
                    array.push(item[j]);
            } else
            {
                array.push(item);
            }
        }
        return array;
    }

    Object.extend(arrayProto, Enumerable);

    if (!arrayProto._reverse)
        arrayProto._reverse = arrayProto.reverse;

    Object.extend(arrayProto, {
        _each: _each,
        clear: clear,
        first: first,
        last: last,
        compact: compact,
        flatten: flatten,
        without: without,
        reverse: reverse,
        uniq: uniq,
        intersect: intersect,
        clone: clone,
        toArray: clone,
        size: size,
        inspect: inspect
    });

    var CONCAT_ARGUMENTS_BUGGY = (function ()
    {
        return [].concat(arguments)[0][0] !== 1;
    })(1, 2)

    if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;

    if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
    if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
})();

function $H(object)
{
    return new Hash(object);
};

var Hash = Class.create(Enumerable, (function ()
{
    function initialize(object)
    {
        this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    }

    function _each(iterator)
    {
        for (var key in this._object)
        {
            var value = this._object[key], pair = [key, value];
            pair.key = key;
            pair.value = value;
            iterator(pair);
        }
    }

    function set(key, value)
    {
        return this._object[key] = value;
    }

    function get(key)
    {
        if (this._object[key] !== Object.prototype[key])
            return this._object[key];
    }

    function unset(key)
    {
        var value = this._object[key];
        delete this._object[key];
        return value;
    }

    function toObject()
    {
        return Object.clone(this._object);
    }

    function keys()
    {
        return this.pluck('key');
    }

    function values()
    {
        return this.pluck('value');
    }

    function index(value)
    {
        var match = this.detect(function (pair)
        {
            return pair.value === value;
        });
        return match && match.key;
    }

    function merge(object)
    {
        return this.clone().update(object);
    }

    function update(object)
    {
        return new Hash(object).inject(this, function (result, pair)
        {
            result.set(pair.key, pair.value);
            return result;
        });
    }

    function toQueryPair(key, value)
    {
        if (Object.isUndefined(value)) return key;
        return key + '=' + encodeURIComponent(String.interpret(value));
    }

    function toQueryString()
    {
        return this.inject([], function (results, pair)
        {
            var key = encodeURIComponent(pair.key), values = pair.value;

            if (values && typeof values == 'object')
            {
                if (Object.isArray(values))
                {
                    var queryValues = [];
                    for (var i = 0, len = values.length, value; i < len; i++)
                    {
                        value = values[i];
                        queryValues.push(toQueryPair(key, value));
                    }
                    return results.concat(queryValues);
                }
            } else results.push(toQueryPair(key, values));
            return results;
        }).join('&');
    }

    function inspect()
    {
        return '#<Hash:{' + this.map(function (pair)
        {
            return pair.map(Object.inspect).join(': ');
        }).join(', ') + '}>';
    }

    function clone()
    {
        return new Hash(this);
    }

    return {
        initialize: initialize,
        _each: _each,
        set: set,
        get: get,
        unset: unset,
        toObject: toObject,
        toTemplateReplacements: toObject,
        keys: keys,
        values: values,
        index: index,
        merge: merge,
        update: update,
        toQueryString: toQueryString,
        inspect: inspect,
        toJSON: toObject,
        clone: clone
    };
})());

Hash.from = $H;

Object.extend(Number.prototype, (function ()
{
    function toColorPart()
    {
        return this.toPaddedString(2, 16);
    }

    function succ()
    {
        return this + 1;
    }

    function times(iterator, context)
    {
        $R(0, this, true).each(iterator, context);
        return this;
    }

    function toPaddedString(length, radix)
    {
        var string = this.toString(radix || 10);
        return '0'.times(length - string.length) + string;
    }

    function abs()
    {
        return Math.abs(this);
    }

    function round()
    {
        return Math.round(this);
    }

    function ceil()
    {
        return Math.ceil(this);
    }

    function floor()
    {
        return Math.floor(this);
    }

    return {
        toColorPart: toColorPart,
        succ: succ,
        times: times,
        toPaddedString: toPaddedString,
        abs: abs,
        round: round,
        ceil: ceil,
        floor: floor
    };
})());

function $R(start, end, exclusive)
{
    return new ObjectRange(start, end, exclusive);
}

var ObjectRange = Class.create(Enumerable, (function ()
{
    function initialize(start, end, exclusive)
    {
        this.start = start;
        this.end = end;
        this.exclusive = exclusive;
    }

    function _each(iterator)
    {
        var value = this.start;
        while (this.include(value))
        {
            iterator(value);
            value = value.succ();
        }
    }

    function include(value)
    {
        if (value < this.start)
            return false;
        if (this.exclusive)
            return value < this.end;
        return value <= this.end;
    }

    return {
        initialize: initialize,
        _each: _each,
        include: include
    };
})());

Hash.toQueryString = Object.toQueryString;